using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Collections.Generic;

namespace SqlServerDriver
{
	public partial class BrokerObjectAddForm : Form
	{
		const String TMPL_1 = @"
IF NOT EXISTS (SELECT 1 from sys.service_message_types where Name='{0}') 
    CREATE MESSAGE TYPE [{0}]
";
		const String TMPL_2 = @"
IF NOT EXISTS (SELECT 1 from sys.service_contracts where Name='{0}')
    CREATE CONTRACT [{0}] ({1})
";

		const String TMPL_3 = @"
    ALTER SERVICE [{0}] (ADD CONTRACT [{1}])
";

		const String TMPL_4 = @"
IF NOT EXISTS (SELECT 1 from sys.service_queues where Name='{0}')
    CREATE QUEUE [{0}] {1}
";
		const String TMPL_5 = @"
      WITH ACTIVATION (STATUS = ON, PROCEDURE_NAME = [{0}], 
                       MAX_QUEUE_READERS = {1}, EXECUTE AS SELF)
";
		const String TMPL_6 = @"
IF NOT EXISTS (SELECT 1 from sys.services where Name='{0}')
    CREATE SERVICE [{0}] ON QUEUE [{1}]
";
		const String TMPL_7 = @"
IF EXISTS (SELECT 1 from sys.services where Name='{0}')
   alter service [{0}] (drop contract [{1}])
";
		const String TMPL_8 = @"
IF EXISTS (SELECT 1 from sys.services where Name='{0}')
   alter service [{0}] (add contract [{1}])
";
		BrokerObjectCollection brokerObjects;
		String script;

		public BrokerObjectAddForm(BrokerObjectCollection brokerObjects)
		{
			InitializeComponent();
			this.brokerObjects = brokerObjects;
			this.script = "";
			this.checkTgt.Checked = true;
			this.checkInit.Checked = true;
			this.textHelp.Text = HELP_TEXT;
			SetActivationStatus();
		}

		public String Script { get { return script; } }

		// make up the script
		private String MakeScript()
		{
			String script = "";
			// If not 
			String newMessage = this.textMessage.Text;
			String newContract = this.textContract.Text;
			String newService = this.textService.Text;
			String newQueue = this.textQueue.Text;
			String activationProc = this.textProc.Text;
			// status -1 = empty 0 = existing 1 = new
			int messageStatus = (newMessage.Length == 0) ? -1 :
								(this.brokerObjects.FindMessage(newMessage) == null) ? 1 : 0;
			int contractStatus = (newContract.Length == 0) ? -1 :
								(this.brokerObjects.FindContract(newContract) == null) ? 1 : 0;
			int serviceStatus = (newService.Length == 0) ? -1 :
								(this.brokerObjects.FindService(newService) == null) ? 1 : 0;
			int queueStatus = (newQueue.Length == 0) ? -1 :
								(this.brokerObjects.FindQueue(newQueue) == null) ? 1 : 0;

			// add message type if not empty
			if (messageStatus != -1)
				script += String.Format(TMPL_1, newMessage);
			// Contract
			// Need at least one message so use default
			String messageName = (messageStatus == -1) ? "DEFAULT" : newMessage;
			// message initiator
			String sentBy = (this.checkInit.Checked && this.checkTgt.Checked) ? "ANY" :
							(this.checkInit.Checked) ? "INITIATOR" :
							(this.checkTgt.Checked) ? "TARGET" : "ANY";
			switch (contractStatus)
			{
				case -1: break; // nothing if empty
				case 1:
					{
						String clause = String.Format(@"[{0}] SENT BY {1}", messageName, sentBy);
						script += String.Format(TMPL_2, newContract, clause);
					}
					break;
				case 0: //           
					{
						// heres the difficult bit 
						// add other message types here 
						List<String> a = new List<String>();
						BrokerObjectCollection.Contract c = this.brokerObjects.FindContract(newContract);
						foreach (BrokerObjectCollection.Service s in c.Services)
							script += String.Format(TMPL_7, s.Name, c.Name);
						script += String.Format(@"    DROP CONTRACT [{0}]", c.Name);
						foreach (BrokerObjectCollection.MessageType m in c.Messages)
						{
							String huh = c.GetUsage(m);
							String script2 = String.Format("{0} SENT BY {1}", m.Name, huh);
							a.Add(script2);
						}
						a.Add(String.Format(@"[{0}] SENT BY {1}", messageName, sentBy));
						String clause = String.Join(",", a.ToArray());

						script += String.Format(TMPL_2, newContract, clause);
						foreach (BrokerObjectCollection.Service s in c.Services)
							script += String.Format(TMPL_8, s.Name, c.Name);
					}
					break;
			}

			// Services 
			// check the queue first
			if (queueStatus != -1)
			{
				// create the quesue
				String activation = "";
				if (activationProc.Length > 0)
				{
					int maxReaders = 0;
					try { maxReaders = Convert.ToInt32(this.textReaders.Text); } 
					catch (System.FormatException) {maxReaders = 1;}
					if (maxReaders <= 0 || maxReaders > 25) maxReaders = 1;
					activation = String.Format(TMPL_5, activationProc, maxReaders);
					// add the queue proc
					script += String.Format(TMPL_PROC, activationProc, newQueue);
				}
				script += String.Format(TMPL_4, newQueue, activation);
			}
			if (serviceStatus != -1)
			{
				if (queueStatus == -1 && serviceStatus == 1) // note if new service
					throw new Exception("Can't add a new service without a queue");
				if (queueStatus != -1)                       // try adding to queue
					script += String.Format(TMPL_6, newService, newQueue);
				if (contractStatus != -1)
					script += String.Format(TMPL_3, newService, newContract);
			}

			if (this.checkGenerateTest.Checked)
			{
				if (messageStatus != -1 &&
					serviceStatus != -1 &&
					contractStatus != -1)
				{
					String testProcName = String.Format("TEST_SEND_{0}", newMessage);
					script += String.Format(TMPL_TESTPROC, testProcName, newService, newContract, newMessage);
				}
			}
			return script;
		}

		private void BtnOk_Click(object sender, EventArgs e)
		{
			try
			{
				// this.m_script = this.MakeScript();
				this.DialogResult = DialogResult.OK;
				this.Close();
			}
			catch (Exception exc)
			{
				MessageBox.Show(this, exc.Message, "Could not generate a safe script", MessageBoxButtons.OK, MessageBoxIcon.Warning);
			}
		}

		private void SetActivationStatus()
		{
			bool enabled = (this.textQueue.Text.Length > 0) ? true : false;
			this.textProc.Enabled = enabled;
			this.textReaders.Enabled = enabled;
			this.checkActivate.Enabled = enabled;
			this.groupActivation.Enabled = enabled;
			BrokerObjectCollection.Queue q = this.brokerObjects.FindQueue(this.textQueue.Text);
			if (q != null)
			{
				this.textProc.Text = q.ActivationProc;
				this.textReaders.Text = q.MaxReaders.ToString();
			}
			else
			{
				if (this.textQueue.Text.Length == 0)
				{
					this.textProc.Text = "";
					this.textReaders.Text = "";
				}
			}
		}

		private void BtnCancel_Click(object sender, EventArgs e)
		{
			this.DialogResult = DialogResult.Cancel;
			this.Close();
		}

		private void BtnPickContract_Click(object sender, EventArgs e)
		{
			TextBox tb = this.textContract;
			String df = tb.Text;
			PickObjectForm dlg = new PickObjectForm(this.brokerObjects, 'C', df);
			dlg.ShowDialog();
			if (dlg.DialogResult == DialogResult.OK)
			{
				tb.Text = dlg.SelectedObjectName;
			}
		}

		private void BtnPickMessage_Click(object sender, EventArgs e)
		{
			TextBox tb = this.textMessage;
			String df = tb.Text;
			PickObjectForm dlg = new PickObjectForm(this.brokerObjects, 'M', df);
			dlg.ShowDialog();
			if (dlg.DialogResult == DialogResult.OK)
			{
				tb.Text = dlg.SelectedObjectName;
			}
		}

		private void BtnPickService_Click(object sender, EventArgs e)
		{
			TextBox tb = this.textService;
			String df = tb.Text;
			PickObjectForm dlg = new PickObjectForm(this.brokerObjects, 'S', df);
			dlg.ShowDialog();
			if (dlg.DialogResult == DialogResult.OK)
			{
				tb.Text = dlg.SelectedObjectName;
			}
		}

		private void BtnPickQueue_Click(object sender, EventArgs e)
		{
			TextBox tb = this.textQueue;
			String df = tb.Text;
			PickObjectForm dlg = new PickObjectForm(this.brokerObjects, 'Q', df);
			dlg.ShowDialog();
			if (dlg.DialogResult == DialogResult.OK)
			{
				tb.Text = dlg.SelectedObjectName;
				// populate the activation stuff
				BrokerObjectCollection.Queue q = this.brokerObjects.FindQueue(dlg.SelectedObjectName);
				if (q != null)
				{
					this.textProc.Text = q.ActivationProc;
					this.textReaders.Text = q.MaxReaders.ToString();
				}
			}
		}

		private void BtnTest_Click(object sender, EventArgs e)
		{
			try
			{
				String script = this.MakeScript();
				this.textScript.Text = script;
			}
			catch (Exception exc)
			{
				this.textScript.Text = "Could not generate a safe script\r\n\r\n" + exc.Message;
			}
		}

		private void BtnCopy_Click(object sender, EventArgs e)
		{
			String script = this.MakeScript();
			if (script != this.textScript.Text)
			{
				String message = "Refresh script before copying to clipboard?\nPress [ No ] to copy the script as shown";
				DialogResult res = MessageBox.Show(message, "Script shown requires refresh", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
				if (res == System.Windows.Forms.DialogResult.Yes)
					this.textScript.Text = script;
			}
			if (this.textScript.Text.Length > 0)
				Clipboard.SetText(this.textScript.Text);
		}

		private void TextQueue_TextChanged(object sender, EventArgs e)
		{
			this.SetActivationStatus();
		}

		// 0 = proc name
		// 1 = queue name
		const String TMPL_PROC = @"
IF OBJECT_ID('{0}') IS NULL 
exec ('
CREATE PROCEDURE [{0}]
AS
    declare		@ConversationHandle uniqueidentifier 
    declare		@MessageBody varbinary(max)
    declare		@MessageType sysname

    set nocount on    
    while 1=1
    begin
        begin transaction
        ;
        waitfor(
        RECEIVE top(1) -- just handle one message at a time
                	@MessageType = message_type_name,  --the type of message received
                	@MessageBody = message_body,      -- the message contents
                	@ConversationHandle = conversation_handle    -- the identifier of the dialog this message was received on
                	FROM [{1}]
        ), TIMEOUT 5000        -- 5000 = 5s. Adjust as required
        -- break the loop when the queue is empty
    	if (@@ROWCOUNT = 0)
        begin
            rollback transaction
    		BREAK
        end 
        
        IF (@MessageType = ''http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog'')
        BEGIN
        	End Conversation @ConversationHandle
        END
        -- else
        -- IF (@MessageType = ''XXXX'')  
        -- BEGIN
        -- TODO: add special message processing
        -- END
        else
        BEGIN
            Print ''{0}''+'' Message Type = ''+@MessageType+'' ''+ cast(@MessageBody as varchar(max))
        	  End Conversation @ConversationHandle
        END
        commit transaction
    end
')
";
		const String TMPL_TESTPROC = @"
IF OBJECT_ID('{0}') IS NULL 
exec('    
    CREATE PROCEDURE [{0}]
    	@user_msg VARCHAR(max)
    AS
        DECLARE @ConversationHandle uniqueidentifier, @msg VarChar(8000);
        
        BEGIN DIALOG CONVERSATION @ConversationHandle
        FROM SERVICE {1}
          TO SERVICE ''{1}''
          ON CONTRACT {2}
        WITH ENCRYPTION = OFF;
     
       ;SEND ON CONVERSATION @ConversationHandle MESSAGE TYPE {3} (@user_msg);
       
    ')
";

	const String HELP_TEXT=@"
Help

Use this Dialog to create a script to add new messages, contracts, queues 
and services. If you are adding a new object enter the name in relevant 
text box. Otherwise use the [...] buttons to select from existing items.

You do not always need to fill in all of the boxes. eg. when you
add a new message to a service you probably will not need to enter 
anything for the queue.

Press [ Refresh ] to view the current script and [ Copy ] to update the 
clipboard with the script.

Refer to external Service broker documentation for background on the 
check-box items and Max readers option.
";
	}
}